use std::{cell::RefCell, fs::read_to_string, num, str::FromStr};

use crate::Puzzle;

const CYCLE_DURATION: u32 = 6;
const ADDITIONAL_DURATION: u32 = 2;

#[derive(Debug, PartialEq)]
struct Fish {
    timer: u32,
    new: bool,
}

impl Fish {
    fn new() -> Self {
        Self {
            timer: CYCLE_DURATION + ADDITIONAL_DURATION,
            new: true,
        }
    }

    fn live_another_day(&mut self) -> Option<Self> {
        if self.timer > 0 {
            self.timer -= 1;
            return None;
        }

        self.timer = CYCLE_DURATION;
        self.new = false;
        Some(Self::new())
    }
}

impl FromStr for Fish {
    type Err = num::ParseIntError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        s.parse::<u32>().map(|value| Fish {
            timer: value,
            new: false,
        })
    }
}

impl From<u32> for Fish {
    fn from(value: u32) -> Self {
        Self {
            timer: value,
            new: false,
        }
    }
}

#[derive(Debug, PartialEq)]
pub struct LanternFish {
    fishes: RefCell<Vec<Fish>>,
    after_days: u32,
}

impl LanternFish {
    fn from(raw_data: &str) -> Self {
        let fishes = raw_data
            .split(",")
            .map(|fish| fish.trim())
            .filter(|fish| !fish.is_empty())
            .map(|fish| fish.parse().unwrap())
            .collect::<Vec<Fish>>();

        Self {
            fishes: fishes.into(),
            after_days: 0,
        }
    }

    fn with_days(self, days: u32) -> Self {
        LanternFish {
            fishes: self.fishes,
            after_days: days,
        }
    }
}

impl Puzzle for LanternFish {
    type Answer = usize;

    fn new() -> Self {
        let input = read_to_string("input/day06.md").unwrap();

        Self::from(&input).with_days(80)
    }

    fn solve_part1(&self) -> Self::Answer {
        for _ in 1..=self.after_days {
            let mut new_births = Vec::new();

            self.fishes.borrow_mut().iter_mut().for_each(|fish| {
                let Some(child) = fish.live_another_day() else {
                    return;
                };

                new_births.push(child);
            });

            self.fishes.borrow_mut().extend(new_births);
        }

        self.fishes.borrow().len()
    }

    fn solve_part2(&self) -> Self::Answer {
        let mut fishes: [usize; 8 + 1] = [0; 8 + 1];

        self.fishes.borrow().iter().for_each(|fish| {
            fishes[fish.timer as usize] += 1;
        });

        for _ in 1..=256 {
            let mut next_day: [usize; 8 + 1] = [0; 8 + 1];

            let ready_to_births = fishes[0];

            for i in 1..=8 {
                next_day[i - 1] = fishes[i];
            }

            next_day[8] = ready_to_births;
            next_day[6] += ready_to_births;

            fishes = next_day;
        }

        fishes.iter().sum::<usize>()
    }
}

#[cfg(test)]
mod test {
    use super::*;

    fn create_instance() -> LanternFish {
        LanternFish::from("3,4,3,1,2")
    }

    #[test]
    fn from() {
        let lantern_fish = create_instance();
        assert_eq!(
            lantern_fish,
            LanternFish {
                fishes: vec![3, 4, 3, 1, 2]
                    .into_iter()
                    .map(|i| i.into())
                    .collect::<Vec<Fish>>()
                    .into(),
                after_days: 0
            }
        );
    }

    #[test]
    fn part1() {
        assert_eq!(create_instance().with_days(18).solve_part1(), 26);
        assert_eq!(create_instance().with_days(80).solve_part1(), 5934);
    }

    #[test]
    fn part2() {
        assert_eq!(create_instance().solve_part2(), 26984457539);
    }
}
